#include "Render.h"
#define SCALE 4

Render::Render(void)
{
	// Initialize the variables
	camX = 0; camY = 0; camZ = 0;
	xrot = 320; yrot = 360; zZoom = 22;
}

void Render::PrepTextures()
{
	// Create a new object to represent the FLOOR, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Floor..." << endl;
	BasicFloor = new renderFloor(1.0000f, 1.0000f, 1.0000f);
	// Create a new object to represent the BLOCK SWITCH, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Block Switch..." << endl;
	BlockSwitch = new renderFloor(0.2000f, 0.2000f, 0.2000f);
	// Create a new object to represent the FIRE FLOOR, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Fire Floor..." << endl;
	FireFloor = new renderFireFloor(1.0000f, 1.0000f, 1.0000f);
	// Create a new object to represent the ICE FLOOR, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Ice Floor..." << endl;
	IceFloor = new renderIceFloor(0.5019f, 0.5000f, 1.0000f);
	// Create a new object to represent the WATER FLOOR, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Water Floor..." << endl;
	WaterFloor = new renderWaterFloor(1.0000f, 1.0000f, 1.0000f);
	// Create a new object to represent the TAIT, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Tait..." << endl;
	Tait = new renderTait(1.000f, 1.000f, 1.000f);
	// Create a new object to represent the IMOBLOCK, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Unmoveable Block..." << endl;
	ImoBlock = new renderImoBlock(1.0f, 1.0f, 1.0f);
	// Create a new object to represent the BLOCK, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Block..." << endl;
	Block = new renderBlock(1.000f, 1.000f, 1.000f);
	// Create a new object to represent the EXIT, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Exit..." << endl;
	Exit = new renderExit(1.000f, 1.000f, 1.000f);
	// Create a new object to represent the SKATES, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Skates..." << endl;
	Skates = new renderBoots(0.000f, 0.000f, 1.000f);
	// Create a new object to represent the STAIRS, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Fireboots..." << endl;
	Fireboots = new renderBoots(1.000f, 0.000f, 0.000f);
	// Create a new object to represent the STAIRS, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Stairs..." << endl;
	Stairs = new renderStairs(1.0, 1.0, 1.0);
	// Create a new object to represent the BOMB, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Bomb..." << endl;
	Bomb = new renderBomb(1.0,1.0,1.0);
	// Create a new object to represent the GRAVEL, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Gravel..." << endl;
	Gravel = new renderGravel(1.0,1.0,1.0);
	// Create a new object to represent the DISSOLVE, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Dissolve..." << endl;
	Dissolve = new renderDissolve(1.0,1.0,1.0);
	// Create a new object to represent the FORCE PAD, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Force..." << endl;
	Force = new renderForce(1,1,1);
	// Create a new object to represent the WALL, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Wall..." << endl;
	Wall = new renderWall(1,1,1);
	// Create a new object to represent the KEY, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Keypot..." << endl;
	Key = new renderKey(1,1,1);
	// Create a new object to represent the GATE, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Gate..." << endl;
	Gate = new renderGate(1,1,1);
	// Create a new object to represent the FROG, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Frog..." << endl;
	Frog = new renderFrog(1,1,1);
	// Create a new object to represent the ANT, passing colour parameters
	// Inform the user on the console that it is loading.
	cout << "Loading Ant..." << endl;
	Ant = new renderAnt(1,1,1);
	// Create a new object to represent the POTATO, passing colour parameters
	// Inform the user on the console that it is loading.
	cout<<"Loading Potato"<<endl;
	Potato = new renderPotato(1,1,1);
}

// Update map takes the map, and new maximum sizes in the x, y, and z
void Render::UpdateMap(vector<vector<vector<Floor*>>> mapPointer, int init_x, int init_y, int init_z)
{
	// reinitialize the variables
	map = mapPointer;
	x = init_x;	y = init_y;	z = init_z;
}

Render::~Render(void)
{
}

void Render::updateSize(int init_x, int init_y, int init_z)
{
	// update just the size of the map
	x = init_x;
	y = init_y;
	z = init_z;
}

void Render::doRender(vector<int> r)
{
	// Call the camera renderer
	Look(r);
	// Call the lighting
	doLighting(r);

	// THE FOLLOWING LOOP!
	// goes the x, y and z represented here by i, j and k
	// looking first at what floor is what kind of floor
	// looking second at what object is on a floor
	// it does this using a switch statement
	// each object already exists, so for whatever it is
	// we ask it's parent object to draw itself at that position.

	//The first loop again does floors
	int i; int j; int k;

	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			for (k = 0; k < z; k++)
			{
				switch(map[i][j][k]->getFloorState())
				{
				case BLOCKFILLED:
					BasicFloor->draw(i, y - j, k);
					break;
				case EXTINGUISHED:
					BasicFloor->draw(i, y - j, k);
					break;
				case ABLAZE:
					FireFloor->draw(i, y - j, k);
					break;
				case WATERFILLED:
					WaterFloor->draw(i, y - j, k);
					break;
				}

				switch(map[i][j][k]->getType())
				{
				case NORMAL:
					BasicFloor->draw(i, y - j, k);
					break;
				case ICE:
					IceFloor->draw(i, y - j, k);
					break;

				case SNORTH:
					Stairs->draw(i, y - j, k, UP);
					break;

				case SSOUTH:
					Stairs->draw(i, y - j, k, DOWN);
					break;

				case SEAST:
					Stairs->draw(i, y - j, k, RIGHT);
					break;

				case SWEST:
					Stairs->draw(i, y - j, k, LEFT);
					break;

				case WNORTH:
					Wall->draw(i, y - j, k, UP);
					break;

				case WSOUTH:
					Wall->draw(i, y - j, k, DOWN);
					break;

				case WEAST:
					Wall->draw(i, y - j, k, RIGHT);
					break;

				case WWEST:
					Wall->draw(i, y - j, k, LEFT);
					break;

				case GRAVEL:
					Gravel->draw(i, y - j, k);
					break;	


				case FORCE:

					switch(map[i][j][k]->getFloorState())
					{
					case UPFLOOR:
						Force->draw(i,y-j,k,UP);
						break;
					case DOWNFLOOR:
						Force->draw(i,y-j,k,DOWN);
						break;
					case RIGHTFLOOR:
						Force->draw(i,y-j,k,RIGHT);
						break;
					case LEFTFLOOR:
						Force->draw(i,y-j,k,LEFT);
						break;
					}
					break;

				case BLOCKSWITCH:

					switch(map[i][j][k]->getFloorState())
					{
					case OPEN:
						BlockSwitch->draw(i,y-j,k);
						break;
					case BLOCKCREATED:
						ImoBlock->draw(i,y-j,k);
						break;
					}
					break;
				}



			}
		}
	}

	//The second loop again does objects
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			for (k = 0; k < z; k++)
			{
				switch(map[i][j][k]->getObjectType())
				{
				case TAIT:
					Tait->draw(i, y - j, k, (Direction) r[3], (Equip) r[4]);
					break;
				case BLOCK:
					Block->draw(i, y - j, k);
					break;
				case EXIT:
					Exit->draw(i, y - j, k);
					break;
				case SKATES:
					Skates->draw(i, y - j, k, UP);
					break;
				case BOOTS:
					Fireboots->draw(i, y - j, k, UP);
					break;
				case IMOBLOCK:
					ImoBlock->draw(i, y - j, k);
					break;	
				case BOMB:
					Bomb->draw(i, y - j, k);
					break;	
				case DISSOLVE:
					Dissolve->draw(i, y - j, k);
					break;

				case REDKEY:
					Key->draw(i, y - j, k, 1.0, 0.0, 0.0);
					break;
				case YELLOWKEY:
					Key->draw(i, y - j, k, 1.0, 1.0, 0.0);
					break;
				case ORANGEKEY:
					Key->draw(i, y - j, k, 1.0, 0.5, 0.0);
					break;
				case GREENKEY:
					Key->draw(i, y - j, k, 0.0, 1.0, 0.0);
					break;

				case REDGATE:
					Gate->draw(i, y - j, k, 1.0, 0.0, 0.0);
					break;
				case GREENGATE:
					Gate->draw(i, y - j, k, 0.0, 1.0, 0.0);
					break;
				case YELLOWGATE:
					Gate->draw(i, y - j, k, 1.0, 1.0, 0.0);
					break;
				case ORANGEGATE:
					Gate->draw(i, y - j, k, 1.0, 0.5, 0.0);
					break;
				case POTATOGATE:
					Gate->draw(i, y - j, k, 0.0, 0.0, 1.0);
					break;
				case POTATO:
					Potato->draw(i, y - j, k);
					break;
				case FROGMONSTER:
					Frog->draw(i, y - j, k);
					break;
				case ANTMONSTER:
					Ant->draw(i, y - j, k);
					break;
				}
			}
		}
	}

	// Redisplay to the screen
	glutPostRedisplay();

}


//Does lighting based on the current level and maybe other factors
void Render::doLighting(vector<int> r){

	glClearDepth(1);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);
	int x_tait = (r[0] * SCALE); int y_tait = (y - r[1]) * SCALE; int z_tait = (r[2] + 1.5) * SCALE;

	if ((int) r[5] <= 30 && ((int) r[5] % 2) == 0)
	{
		GLfloat light_ambient[] = { .9, .2, .2, 1.0};
		GLfloat light_diffuse[] = { .9, .2, .2, 1.0};
		GLfloat light_specular[] = { .9, .2, .2, 1.0};

		GLfloat light_position[] = {x_tait, y_tait, z_tait, 1.0 };


		glLightfv(GL_LIGHT0, GL_POSITION, light_position);
		glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
		glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.0);
		glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.3);
		glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0);
		glEnable(GL_LIGHT0);

	} else {
		GLfloat light_ambient[] = { .2, .2, .2, 1.0};
		GLfloat light_diffuse[] = { .9, .9, .9, 1.0};
		GLfloat light_specular[] = { .6, .6, .6, 1.0};

		GLfloat light_position[] = {x_tait, y_tait, z_tait, 1.0 };


		glLightfv(GL_LIGHT0, GL_POSITION, light_position);
		glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
		glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.0);
		glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.3);
		glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0);
		glEnable(GL_LIGHT0);

	}

}

// Named unProject, no longer unProjects
// We use this to determine appropriate
// camera rotation, and distance
void Render::unProject(int x, int y)
{

	GLint viewport[4];
	GLdouble modelview[16];
	GLdouble projection[16];
	GLfloat winZ;

	glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
	glGetDoublev( GL_PROJECTION_MATRIX, projection );
	glGetIntegerv( GL_VIEWPORT, viewport );

	glReadPixels( x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );

	gluUnProject( x, y, winZ, modelview, projection, viewport, &camX, &camY, &camZ);

	int diffx = x - lastx; //check the difference between the current x and the last x position
	int diffy = y - lasty; //check the difference between the current y and the last y position
	lastx = x; //set lastx to the current x position
	lasty = y; //set lasty to the current y position

	//Conditionals so you don't skip around, dirty fix to this problem
	if(diffx<=4)
		xrot += diffy; //set the xrot to xrot with the addition of the difference in the y position
	if(diffy<=4)
		yrot += diffx;// set the xrot to yrot with the addition of the difference in the x position

}

// Called by dispatch, tells us when there are new potential 
// camera coordinates
void Render::changeCamera(int x, int y)
{
	unProject(x, y);
}

// Changes the zoom factor, unfortunately
// glut doesn't support mouse wheel, 
// we have used the keyboard keys
void Render::wheel(int dir){
	if(dir > 0 && zZoom <=80){
		zZoom++;
	}
	else if(zZoom > 13)
	{
		zZoom--;
	}

}

// Modifies the camera according to many different parameters
// determined at various locations of render.
void Render::Look(vector<int> r)
{

	// These are the limits of camera rotation
	// to prevent it from moving too far in any one direction.
	if(xrot<=280){
		xrot = 280;
	}
	if(xrot>=360){
		xrot =360;
	}
	if(yrot<=280){
		yrot=280;
	}
	if(yrot>=440){
		yrot=440;
	}

	// Determines the centre of the map based on the maximum size of the map
	// x-axis centre topmost
	int xCentre = ((r[0]) * SCALE);
	// y-axis centre topmost
	int yCentre = (((y - r[1]) * SCALE));
	// z-axis centre topmost
	int zCentre = (r[2] * SCALE);
	// Finds the Centre for a different purpose
	int mapCentX = (x*SCALE)/2;
	int mapCentY = (y*SCALE)/2;

	// look at the centre, from a distant co-ordinate, zZoom along the z-axis
	gluLookAt (xCentre, yCentre, zZoom, xCentre, yCentre, zCentre, 0.0, 1.0, 1.0); //camera position, x,y,z, looking at x,y,z, Up Positions of the camera	

	// translate our camera to the centre appropriately
	glTranslatef(xCentre, yCentre, 0);
	glRotatef(xrot, 1.0, 0.0, 0.0);  //rotate our camera on the x-axis (left and right)
	glRotatef(yrot, 0.0, 0.0, 1.0);  //rotate our camera on the y-axis (up and down)
	glTranslatef(-xCentre, -yCentre, -0);

	glPushMatrix();

	glRotatef(90, 1.0, 0.0, 0.0);
	glScalef(0.1, 0.1, 0.1);

	glTranslatef(xCentre, yCentre, 0);
	glRotatef(xrot, 1.0, 0.0, 0.0);  //rotate our camera on the x-axis (left and right)
	glRotatef(yrot, 0.0, 0.0, 1.0);  //rotate our camera on the y-axis (up and down)


	glRotatef(-xrot, 1.0, 0.0, 0.0);  //rotate our camera on the x-axis (left and right)
	glRotatef(-yrot, 0.0, 0.0, 1.0);  //rotate our camera on the y-axis (up and down)

	glTranslatef(-xCentre, -yCentre, -0);

	glPopMatrix();

}